package microarray;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.zip.GZIPInputStream;

public class DataSet extends MicroArrayElement
{

	Platform platform;
	private final String baseFTPaddress = "ftp://ftp.ncbi.nih.gov/pub/geo/DATA/SOFT/GDS/";
	private final String baseGzFileName = "ID_HERE.soft.gz";
	private ArrayList<SampleData> sampleDataList;
	private String title;
	private String description;

	public DataSet(String id, Platform platform) throws IOException
	{
		super(id);
		parent = platform.parent;
		this.sampleDataList = new ArrayList<SampleData>();
		this.platform = platform;
	}

	// A class that's just there to store a little information about a sample
	// before it's written:
	private class SampleData
	{
		String id, type, description;

		public SampleData(String id, String type, String description)
		{
			this.id = id;
			this.type = type;
			this.description = description;
		}

		public void writeData(PrintStream out)
		{
			out.println(id + "\t" + type + "\t" + description + "\t"
					+ platform.isControl(id));
		}
	}

	private String outputFile()
	{
		return (id + ".txt");
	}

	private String gzFileName()
	{
		return baseGzFileName.replace("ID_HERE", id);
	}

	private URL getFTPURL()
	{
		try
		{
			return new URL(baseFTPaddress + gzFileName());
		} catch (IOException e)
		{
			printDebug("Error updating URL " + e);
			return null;
		}
	}

	public void scrapeAndRecord() throws IOException
	{
		if (parent.overwriteWhenFileExists()
				|| !(new File(getDir() + outputFile()).exists()))
		{
			String zipFile = getDir() + gzFileName();
			String tmpFile = getDir() + id + "_tmp.txt";

			printDebug("downloading: " + gzFileName());

			new GZDownloader().doDownload(id, 5000, 10);

			printDebug("unpacking: " + gzFileName());
			unpackGZ(zipFile, tmpFile);
			printDebug("reading: " + tmpFile);
			readGdsData(tmpFile);
			printDebug("deleting files for " + id);
			new File(zipFile).delete();
			new File(tmpFile).delete();
			record();
		}
	}

	private class GZDownloader extends Downloader
	{
		public void download() throws IOException
		{
			String fileName = getDir() + gzFileName();
			URLConnection c = getFTPURL().openConnection();
			BufferedInputStream in = new BufferedInputStream(c.getInputStream());
			FileOutputStream out = new FileOutputStream(fileName);

			int i = 0;
			byte[] bytesIn = new byte[1024];
			while ((i = in.read(bytesIn)) >= 0)
			{
				out.write(bytesIn, 0, i);
			}
			out.close();
			in.close();
		}
	}

	private void unpackGZ(String zipFileName, String outFilename)
			throws IOException
	{
		// BufferedInputStream in = new BufferedInputStream(new
		// FileInputStream(fileName));
		GZIPInputStream gzipInputStream = new GZIPInputStream(
				new FileInputStream(zipFileName));
		FileOutputStream out = new FileOutputStream(outFilename);

		// Transfer bytes from the compressed file to the output file
		byte[] buf = new byte[1024];
		int len;
		while ((len = gzipInputStream.read(buf)) > 0)
		{
			out.write(buf, 0, len);
		}

		// Close the file and stream
		gzipInputStream.close();
		out.close();
	}

	private void readGdsData(String fileName) throws IOException
	{
		// identify the lines
		final String datasetTitle = "!dataset_title = ";
		final String datasetDescription = "!dataset_description = ";
		final String sampleIdPrefix = "!subset_sample_id = ";
		final String typePrefix = "!subset_type = ";
		final String descriptionPrefix = "!subset_description = ";

		// properties of the samples in the subset
		ArrayList<String> sampleIds = new ArrayList<String>();
		String type = "";
		String sampleDescription = "";

		BufferedReader in = new BufferedReader(new FileReader(fileName));
		String line = in.readLine();
		boolean endSubset;
		while (line != null)
		{
			if (line.contains(datasetTitle))
			{
				title = line.substring(datasetTitle.length());
				line = in.readLine();
			} else if (line.contains(datasetDescription))
			{
				description = line.substring(datasetDescription.length());
				line = in.readLine();
			} else if (line.contains("^SUBSET"))
			{
				endSubset = false;
				line = in.readLine();
				while (!endSubset && line != null)
				{
					if (line.contains("^DATASET") || line.contains("^SUBSET"))
					{
						addSubset(sampleIds, type, sampleDescription);
						sampleIds.clear();
						type = "";
						sampleDescription = "";
						endSubset = true;
					} else
					{
						if (line.contains(descriptionPrefix))
							sampleDescription = line
									.substring(descriptionPrefix.length());
						else if (line.contains(typePrefix))
							type = line.substring(typePrefix.length());
						else if (line.contains(sampleIdPrefix))
						{
							line = line.substring(sampleIdPrefix.length());
							String[] is = line.split(",");
							for (String id : is)
								sampleIds.add(id.trim());
						}
						line = in.readLine();
					}
				}
			} else
				line = in.readLine();
		}
		in.close();
	}

	private void addSubset(ArrayList<String> sampleIds, String type,
			String description)
	{
		String[] controlWords = { "control", "0 day", "untreated" };
		for (String id : sampleIds)
		{
			sampleDataList.add(new SampleData(id, type, description));
			platform.setControl(id, false);
			for (String c : controlWords)
			{
				if (type.contains(c) || description.contains(c))
					platform.setControl(id, true);
			}
		}
	}

	private void record() throws FileNotFoundException
	{
		File f = new File(getDir() + outputFile());
		printDebug("writing to " + f);
		FileOutputStream out = new FileOutputStream(f);
		PrintStream p = new PrintStream(out);
		for (SampleData s : sampleDataList)
		{
			s.writeData(p);
		}
	}

	@Override
	public void build() throws IOException
	{

	}

	@Override
	public String getDir()
	{
		return platform.getDir();
	}

	public String getTitle()
	{
		return title;
	}

	public String getDescription()
	{
		return description;
	}
}